package org.railage.server;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Properties;

import org.apache.log4j.Logger;
import org.railage.common.protocol.Message;

/**
 * Class opening ServerSocket and accepting connections from multiple clients.
 * For each connection opens ConnectionHandler. Port number for ServerSocket is
 * given in SETTINGS_INI file.
 * 
 * @author Cyril Fertikov
 * 
 */
public class Server {

	/**
	 * File containing server preferences, such as port number
	 */
	public static final String SETTINGS_INI = "settings.ini";

	private static final Logger log = Logger.getLogger(Server.class);

	private int port = 0;

	public Server() {
		Properties prop = new Properties();
		try {
			prop.load(this.getClass().getClassLoader()
					.getResourceAsStream(SETTINGS_INI));
			this.port = Integer.parseInt(prop.getProperty("port"));
		} catch (Exception e) {
			log.error("Error while reading settings.ini file");
			System.exit(1);
		}
		try {
			ServerSocket socket = new ServerSocket(port);
			try {
				log.info("Server started.");
				while (true) {
					Socket incoming = socket.accept();
					ConnectionHandler r = new ConnectionHandler(incoming);
					Thread t = new Thread(r);
					t.start();
				}
			} finally {
				socket.close();
			}
		} catch (IOException e) {
			log.error("ServerSocket IOException:", e);
		}
	}

	public static void main(String[] args) {
		new Server();
	}
}

/**
 * Client connection handler. Gets messages from client and calls reflected
 * methods of Service class.
 * 
 * @author Cyril Fertikov
 * 
 */
class ConnectionHandler implements Runnable {

	private static final Logger log = Logger.getLogger(ConnectionHandler.class);

	private Socket socket = null;
	private String remoteHost = null;
	private ObjectOutputStream out = null;
	private ObjectInputStream in = null;

	public ConnectionHandler(Socket socket) {
		this.socket = socket;
		this.remoteHost = socket.getInetAddress().getHostAddress();
	}

	public void run() {
		try {
			try {
				log.info("Client " + remoteHost + " connected");
				Service db = new Service();
				out = new ObjectOutputStream(socket.getOutputStream());
				in = new ObjectInputStream(socket.getInputStream());
				Message req = null;
				while ((req = (Message) in.readObject()) != null) {
					if (req.getMethod().equals("disconnect")) {
						break;
					} else {
						try {
							Method method = db.getClass().getDeclaredMethod(
									req.getMethod(),
									new Class[] { Object.class });
							Object obj = method.invoke(db,
									new Object[] { req.getObj() });
							out.writeObject(new Message(obj, 0));
						} catch (NoSuchMethodException e) {
							log.error("Attempt to call nonexistent method: "
									+ e.getMessage());
							out.writeObject(new Message(null, 0));
						}
					}
				}
			} finally {
				in.close();
				out.close();
				socket.close();
				log.info("Client " + remoteHost + " disconnected");
			}
		} catch (Exception e) {
			log.error("Error occured:", e);
		}
	}

}